﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace Integrity
{
  public sealed class ManyToOne<TOwner, TValue> : ICollectonProp<TOwner, TValue>
    where TValue : class
    where TOwner : class
  {
    public bool CascadeDeletion { get; private set; }
    private readonly Func<TOwner, ICollection<TValue>> _storageGetter;
    private readonly Action<TOwner, ObservableCollection<TValue>> _observableCollectionStorageSetter;
    private readonly Func<OneToMany<TValue, TOwner>> _corrProp;
    public OneToMany<TValue, TOwner> CorrProp { get { return _corrProp(); } }

    bool IProperty.IsCollection { get { return true; } }
    RelationKind IProperty.Relation { get { return RelationKind.ManyToOne; } }
    object IProperty.GetValue(object owner) { return GetValue((TOwner)owner); }
    IList ICollectonProp.GetValue(object owner) { return (IList)GetValue((TOwner)owner); }
    void IProperty.Reset(object owner) { Reset((TOwner)owner); }
    IProperty IProperty.CorrProp { get { return CorrProp; } }

    public void Reset(TOwner owner)
    {
      GetValue(owner).Clear();
    }

    public ManyToOne(
      Func<TOwner, ObservableCollection<TValue>> storageGetter,
      Action<TOwner, ObservableCollection<TValue>> observableCollectionStorageSetter, 
      Func<OneToMany<TValue, TOwner>> corrProp, bool cascadeDeletion)
    {
      Integrity.Register<TOwner>(this);
      CascadeDeletion = cascadeDeletion;
      _storageGetter = storageGetter;
      _observableCollectionStorageSetter = observableCollectionStorageSetter;
      _corrProp = corrProp;
    }

    public ICollection<TValue> GetValue(TOwner owner)
    {
      var collection = _storageGetter(owner);
      if (collection is ISpecial)
        return collection;
      if (collection != null)
        throw new Exception("You initialized collection before call me!");
      var res = new MyList(owner, this);
      _observableCollectionStorageSetter(owner, res);
      return res;
    }

    private interface ISpecial { } // Marker interface

    private class MyList : ObservableCollection<TValue>, ISpecial
    {
      private readonly ManyToOne<TOwner, TValue> _ownerProp;
      private readonly TOwner _owner;

      public MyList(TOwner owner, ManyToOne<TOwner, TValue> ownerProp)
      {
        _ownerProp = ownerProp;
        _owner = owner;
      }

      protected override void InsertItem(int index, TValue newItem)
      {
        if (Contains(newItem)) return;
        base.InsertItem(index, newItem);
        AddIntegrity(newItem);
      }
      protected override void SetItem(int index, TValue newItem)
      {
        var replaced = Items[index];
        base.SetItem(index, newItem);
        RemoveIntegrity(replaced);
        AddIntegrity(newItem);
      }
      protected override void RemoveItem(int index)
      {
        var removedItem = Items[index];
        base.RemoveItem(index);
        RemoveIntegrity(removedItem);
      }
      protected override void ClearItems()
      {
        var oldList = new List<TValue>(Items);
        base.ClearItems();
        ClearIntegrity(oldList);
      }

      private void AddIntegrity(TValue item)
      {
//        var corrProp = _owner.Key.CorrProp(item);
//        Debug.Assert(ReferenceEquals(corrProp.Owner, item));

        _ownerProp.CorrProp.SetValue(item, _owner);

//        var collectionProp = corrProp as ICollectionProperty;
//        if (collectionProp != null)
//          collectionProp.Values.Add(_owner.Owner);
      }
      private void RemoveIntegrity(TValue item)
      {
        if (_ownerProp.CascadeDeletion)
        {
          Integrity.Release(item);
          return;
        }

        _ownerProp.CorrProp.Reset(item);

//        var referenceProp = corrProp as IReferenceProperty;
//        if (referenceProp != null)
//          referenceProp.Value = null;

//        var collectionProp = corrProp as ICollectionProperty;
//        if (collectionProp != null)
//          collectionProp.Values.Remove(_owner.Owner);
      }
      private void ClearIntegrity(IEnumerable<TValue> oldList)
      {
        foreach (var item in oldList)
          RemoveIntegrity(item);
      }
    }
  }
}